package pl.puntin.punclient.networkadapter;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import pl.puntin.punclient.messages.ConnectionLostMessage;
import pl.puntin.punclient.messages.Message;
import pl.puntin.punclient.messages.MessageTypesMap;
import pl.puntin.punclient.messages.UnknowMessage;

public class NetworkAdapter
{
	/**
	 * Kolejka blokujaca dla przychodzacych wiadomosci.
	 */
	private final BlockingQueue<Message> incomingQueue;

	/**
	 * Kolejka blokujaca dla wychodzacych wiadomsci.
	 */
	private final BlockingQueue<Message> outgoingQueue;
	/**
	 * Watek pobierajacy wiadomosci
	 */
	private final TcpListener listener;
	/**
	 * Watek wysylajacy wiadomosci
	 */
	 private final TcpSender sender;

	/**
	 * Socket
	 */
	private final Socket socket;

	public NetworkAdapter(final String host, final int port)
			throws UnknownHostException, IOException
	{
		socket = new Socket(host, port);

		incomingQueue = new LinkedBlockingQueue<Message>();
		outgoingQueue = new LinkedBlockingQueue<Message>();
		listener = new TcpListener();
		sender = new TcpSender();

	}

	public void startListen()
	{
		new Thread(listener).start();
	}
	
	public void startSending()
	{
		new Thread(sender).start();
	}

	public void send(final Message message) throws InterruptedException
	{
		outgoingQueue.put(message);
	}

	public Message receive() throws InterruptedException
	{
		return incomingQueue.take();
	}

	/**
	 * To robi pan kamil
	 * 
	 * @author kakus
	 * 
	 */
	private class TcpListener implements Runnable
	{
		private static final int BUFFOR_SIZE = (640*480)+1;
		private final InputStream inputStream;

		public TcpListener() throws IOException
		{
			inputStream = socket.getInputStream();
		}

		@Override
		public void run()
		{
			byte[] buffor = new byte[BUFFOR_SIZE];
			while ( true )
			{
				try
				{
					int messageLenght = inputStream.read(buffor);
					if (messageLenght <= 0)
					{
						incomingQueue.put(new ConnectionLostMessage());
						break;
					}
					byte messageCode = buffor[0];

					/*
					 * Pobieramy klase wiadmosci
					 */
					Class<? extends Message> messageClass = MessageTypesMap
							.getClass(messageCode);
					if (messageClass == null)
					{
						incomingQueue.put(new UnknowMessage(messageCode));
					}
					else
					{
						/*
						 * Szukamy konstruktora dzieki ktoremu bedzie mozliwe
						 * zbudowanie obiektu naszej klasy
						 */
						try
						{
							Class[] constructorArguments = { byte[].class };
							Constructor<? extends Message> constructor = messageClass
									.getConstructor(constructorArguments);
							Object[] arguments = new Object[]{ buffor };
							incomingQueue.put(constructor.newInstance(arguments));
						}
						/*
						 * Jesli nie ma takiego konstruktora to znaczy ze wiadomosc nie
						 * ma dancych
						 */
						catch (NoSuchMethodException e)
						{
							incomingQueue.put(messageClass.newInstance());
						}
						catch (InvocationTargetException e)
						{
							// TODO blad podczas tworzenia message z konstruktora byte[]
							e.printStackTrace();
						}
					}
				}
				catch (IOException e)
				{
					// TODO blad podczas odczytu z socketa
					e.printStackTrace();
					break;
				}
				catch (InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				catch (SecurityException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				catch (IllegalArgumentException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				catch (InstantiationException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				catch (IllegalAccessException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * To robi pan lukasz
	 * 
	 * @author lukasz
	 * 
	 */
	private class TcpSender implements Runnable
	{
		private final OutputStream outputStream;

		public TcpSender() throws IOException
		{
			outputStream = socket.getOutputStream();
		}

		@Override
		public void run()
		{
			while (true)
			{
				try
				{
					Message message = outgoingQueue.take();
					outputStream.write(message.getByteForm());
				}
				catch (IOException e)
				{
					// TODO blad podczas wysylania
					e.printStackTrace();
					break;
				}
				catch (InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}
